
#ifndef RENDERER_H_
#define RENDERER_H_

#include "Core.h"
#include "RenderObject.h"
#include "RenderShapes.h"

namespace p2
{

class Renderer
{
public:
    typedef void (* RenderObjectCallback)(Renderer& , uint32 , RenderObject& , Matrix4<real>& );

public:
	inline Renderer();
	virtual ~Renderer();

    /*
     * access
     */
	inline void setClearRgba(const Vector4<real>& clearRgba);
	inline const Matrix4<real>& getBaseModelViewMatrix() const;
	inline void setBaseModelViewMatrix(const Matrix4<real>& baseModelViewMatrix);
	inline RenderProgram& getCurrentProgram();
	inline RenderObject::List& getObjectList(int32 program);
	inline RenderProgram& getProgram(int32 program);
	inline const Matrix4<real>& getProjectionMatrix() const;
	inline void setProjectionMatrix(const Matrix4<real>& projectionMatrix);
	inline RenderObjectCallback& getRenderObjectCallback();
	inline void setRenderObjectCallback(RenderObjectCallback renderObjectCallback);
	inline GLuint getVtUnitQuadVertexData11Id() const;
	inline void setVtUnitQuadVertexData11Id(GLuint vtUnitQuadVertexData11Id);

    bool loadShaders(uint32 type, char const* vertShaderFile, char const* fragShaderFile);

    inline bool add(RenderObject& object);
    inline void switchProgram(int32 program);
	inline bool setObjectState(RenderObject& object);

	void setModelView(Matrix4<real> const& mv);
    void clear();
    void render();

    void draw(GLenum mode, RenderVertexVC const* vertices, GLsizei count);

    static GLuint createVbo(const void* data, int size, GLenum target, GLenum usage);
    static void deleteVbo(GLuint& vertexBuffer);

private:
    bool loadFile(char const* file, String& out);
    bool compileShader(GLuint* shader, GLenum type, char const* file);
    bool linkProgram(GLuint prog);
    bool validateProgram(GLuint prog);

private:
    Vector4<real> _clearRgba;
    RenderObjectCallback _renderObjectCallback;
    Matrix4<real> _projectionMatrix;
    Matrix4<real> _baseModelViewMatrix;

    RenderProgram _programs[NUM_PROGRAMS];
    int32 _currentProgram;
    RenderObject::List _objects[NUM_PROGRAMS];

	GLuint _vtUnitQuadVertexData11Id;
};

inline Renderer::Renderer()
    : _clearRgba(Vector4<real>::ZERO), _renderObjectCallback(NULL), _currentProgram(PROGRAM_VC), _vtUnitQuadVertexData11Id(0)
{
    _vtUnitQuadVertexData11Id = createVbo(&vtUnitQuadVertexData11[0], sizeof(vtUnitQuadVertexData11), GL_ARRAY_BUFFER, GL_STATIC_DRAW);
	_projectionMatrix = _baseModelViewMatrix = Matrix4<real>::makeIdentity();
}

/*
 * access
 */
inline void Renderer::setClearRgba(const Vector4<real>& clearRgba)
{
	_clearRgba = clearRgba;
}
inline const Matrix4<real>& Renderer::getBaseModelViewMatrix() const
{
	return _baseModelViewMatrix;
}

inline void Renderer::setBaseModelViewMatrix(const Matrix4<real>& baseModelViewMatrix)
{
	_baseModelViewMatrix = baseModelViewMatrix;
}

inline RenderProgram& Renderer::getCurrentProgram()
{
	return _programs[_currentProgram];
}

inline RenderObject::List& Renderer::getObjectList(int32 program)
{
	return _objects[program];
}

inline RenderProgram& Renderer::getProgram(int32 program)
{
	return _programs[program];
}

inline const Matrix4<real>& Renderer::getProjectionMatrix() const
{
	return _projectionMatrix;
}

inline void Renderer::setProjectionMatrix(const Matrix4<real>& projectionMatrix)
{
	_projectionMatrix = projectionMatrix;
}

inline Renderer::RenderObjectCallback& Renderer::getRenderObjectCallback()
{
	return _renderObjectCallback;
}

inline void Renderer::setRenderObjectCallback(RenderObjectCallback renderObjectCallback)
{
	_renderObjectCallback = renderObjectCallback;
}

inline GLuint Renderer::getVtUnitQuadVertexData11Id() const
{
	return _vtUnitQuadVertexData11Id;
}

inline void Renderer::setVtUnitQuadVertexData11Id(GLuint vtUnitQuadVertexData11Id)
{
	_vtUnitQuadVertexData11Id = vtUnitQuadVertexData11Id;
}

inline bool Renderer::add(RenderObject& object)
{
	if (object.getType()>=PROGRAM_VC&&object.getType()<=PROGRAM_VTC)
	{
		_objects[object.getType()].push_back(object);
		return true;
	}
	return false;
}

inline void Renderer::switchProgram(int32 program)
{
    glUseProgram(getProgram(program).getId());
    _currentProgram = program;
}

inline bool Renderer::setObjectState(RenderObject& object)
{
    glBindBuffer(GL_ARRAY_BUFFER, object.getVerticesId());                    // activate vbo id to use
    GLint bufferSize = 0;
    glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &bufferSize);
    if ( !bufferSize ) {
        return false;
    }
    object.setNumCoords((bufferSize >> 1) / sizeof(GLfloat));
	return true;
}

} /* namespace p2 */

#endif	//RENDERER_H_
